﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using Microsoft.Kinect;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        // Used for generating a random location on screen
        private Random rand = new Random();

        // Used to defining the pool and size of the spots
        private Ellipse[] spots;
        private int currentSpot = 0;
        private ColorImageFormat _resolution;
        private byte[] pixelArray = new byte[4];
        private Ellipse spot;
        private int _updateSpeed;
        private int _totalSpots;

        // Used to continually update the display
        private DispatcherTimer timer = new DispatcherTimer();
        private int update = 0;

        //Kinect properties
        private KinectSensor kinect;
        private BitmapSource BMP;
        private short[] depthArray = new short[307200];
        public enum KinectNumber { ONE = 0, TWO = 1 };
        public KinectNumber _kNumber = KinectNumber.ONE;
        public int near { get; set;}
        public int far { get; set; }
        
        #region Constructor and Window Update
        public MainWindow(int maxSpots, ColorImageFormat resolution, int totalSpots, int spotSize, int updateSpeed, int near, int far)
        {
            InitializeComponent();
            
            spots = new Ellipse[maxSpots];
            this.resolution = resolution;
            this.spotSize = spotSize;
            for (int x = 0; x < spots.Length; x++)
            {
                spots[x] = new Ellipse();
                spots[x].Height = spotSize;
                spots[x].Width = spotSize;
                spots[x].Visibility = Visibility.Hidden;
                canvas.Children.Add(spots[x]);
            }

            // totalSpots hides all of the spots in the array and therefore must be called after the array has been initialized
            this.totalSpots = totalSpots;
            this.updateSpeed = updateSpeed;
            kNumber = KinectNumber.ONE;

            this.near = near;
            this.far = far;
        }

        private void window_Loaded(object sender, RoutedEventArgs e)
        {
            refreshKinect();

            // This timer is primarily used to update the spots
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = new TimeSpan(1000);
            timer.Start();
        }

        private void window_Closed(object sender, EventArgs e)
        {
            kinect.Stop();
            kinect = null;
        }

        // This timer is primarily used to update the spots
        void timer_Tick(object sender, EventArgs e)
        {
            for (int x = 0; x < totalSpots; x++)
            {
                if (x % updateSpeed == update)
                    randomSpot();
            }

            update++;
            if (update >= updateSpeed)
            {
                update = 0;
            }
        }
        #endregion Constructor and Window Update

        #region Spot Related
        public int spotSize { get; set; }

        // Sets the update speed, and ensures no division by zero
        public int updateSpeed
        {
            get
            {
                return _updateSpeed;
            }
            set
            {
                if (value == 0)
                    _updateSpeed = 1;
                else
                    _updateSpeed = value;
            }
        }

        // This sets the total spots variable and hides all the other spots
        public int totalSpots
        {
            get
            {
                return _totalSpots;
            }
            set
            {
                _totalSpots = value;
                for (int x = value; x < spots.Length; x++)
                {
                    spots[x].Visibility = Visibility.Hidden;
                }
            }
        }

        // Select a random spot from the bitmap image and creates a circle at that location
        public void randomSpot()
        {            
            // If there is no kinect image do nothing
            if (BMP == null) return;

            // select the random spot
            int x = rand.Next((int)BMP.Width);
            int y = rand.Next((int)BMP.Height);

            // Get the spot to update
            spot = getSpot();

            // Get a four bite pixel aray from the kinect bitmap and create a brush of the appropriate color
            BMP.CopyPixels(new Int32Rect(x, y, 1, 1), pixelArray, 4 * 1280, 0);
            spot.Fill = new SolidColorBrush(Color.FromRgb(pixelArray[2], pixelArray[1], pixelArray[0]));

            int heightOffset = 640 * (int)((480 / BMP.Height ) * y);
            if (depthArray[heightOffset + (int)((640 / BMP.Width) * x)] > near && depthArray[heightOffset + (int)((640 / BMP.Width) * x)] < far)
            {
                Canvas.SetLeft(spot, x - (spotSize / 2));
                Canvas.SetTop(spot, y - (spotSize / 2));
                spot.Visibility = Visibility.Visible;
            }
            else { spot.Visibility = Visibility.Hidden; }

            // Randomize front-to-back to remove wave type effect 
            Canvas.SetZIndex(spot, (int)rand.Next(spots.Length));
        }

        // Gets a spot from the rotating pool
        public Ellipse getSpot()
        {
            currentSpot++;
            if (currentSpot >= totalSpots)
            {
                currentSpot = 0;
            }

            // These need to moved to the appropriate place but it works here
            spots[currentSpot].Height = spotSize;
            spots[currentSpot].Width = spotSize;
            return spots[currentSpot];

        }
        #endregion Spot Related

        #region Kinect related
        void kinect_AllFramesReady(object sender, AllFramesReadyEventArgs e)
        {
            using (ColorImageFrame Image = e.OpenColorImageFrame())
            {
                if (Image != null)
                {
                    byte[] tempArray = new byte[Image.PixelDataLength];
                    Image.CopyPixelDataTo(tempArray);

                    BMP = BitmapSource.Create(Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32,
                        null, tempArray, Image.Width * Image.BytesPerPixel);
                }
            }

            using (DepthImageFrame depth = e.OpenDepthImageFrame())
            {
                if (depth != null)
                {
                    depth.CopyPixelDataTo(depthArray);
                }
            }
        }
        
        // May need to depricate this method in favor of the new default Kinect chooser
        private void refreshKinect()
        {            
            // Attempts to unitilize the Kinect if this is to change the resolution
            // This is probably where the performance problem is
            if (kinect != null)
            {
                kinect.Stop();
                kinect = null;
                GC.Collect();
            }

            // The rest of this method is all standard Microsoft Kinect code
            kinect = KinectSensor.KinectSensors[(int)kNumber];

            kinect.ColorStream.Enable(resolution);
            kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
            kinect.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(kinect_AllFramesReady);
            kinect.Start();
        }

        // Changes the screen size and resets the Kinect resolution
        public ColorImageFormat resolution
        {
            get { return _resolution; }
            set
            {
                _resolution = value;
                refreshKinect();
                if (_resolution == ColorImageFormat.RgbResolution640x480Fps30)
                {
                    window.Width = 640 + 15;
                    window.Height = 480 + 40;
                }
                else if (_resolution == ColorImageFormat.RgbResolution1280x960Fps12)
                {
                    window.Width = 1280 + 15;
                    window.Height = 960 + 40;
                }
            }
        }

        //Changes the selected Kinect
        public KinectNumber kNumber
        {
            get { return _kNumber; }
            set
            {
                _kNumber = value;
                refreshKinect();
            }
        }
        #endregion Kinect related
    }
}